/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pl.raflik.queryxml.saxon.xquery;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.WeakHashMap;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import net.sf.saxon.s9api.DOMDestination;
import net.sf.saxon.s9api.Destination;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.SaxonApiUncheckedException;
import net.sf.saxon.s9api.XQueryCompiler;
import net.sf.saxon.s9api.XQueryEvaluator;
import net.sf.saxon.s9api.XQueryExecutable;
import net.sf.saxon.s9api.XdmItem;
import net.sf.saxon.s9api.XdmNode;
import org.openide.util.NbBundle;
import org.openide.xml.XMLUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import pl.raflik.queryxml.langservice.InvalidQueryException;
import pl.raflik.queryxml.langservice.QueryLanguage;
import pl.raflik.queryxml.langservice.QueryResolverException;
import pl.raflik.queryxml.lib.exception.NullArgumentException;

/**
 *
 * @author raflik
 */
public class SaxonXQueryResolver implements QueryLanguage {

    private XQueryCompiler xquery = null;
    private final WeakHashMap<String, XQueryExecutable> exprCache = new WeakHashMap<String, XQueryExecutable>();
    private static final String LANG_XQUERY1 = "XQuery 1.0 (Saxon)";
    private static final String MIME_TYPE = "text/x-xquery1";

    private XQueryExecutable cacheAwareCompile(String query, boolean addToCache) throws SaxonApiException {
        if (query == null) {
            throw new NullArgumentException("query");
        }

        XQueryExecutable expr;
        if (exprCache.containsKey(query)) {
            expr = exprCache.get(query);
        } else {
            expr = getXQueryCompiler().compile(query);
            if (addToCache) {
                exprCache.put(query, expr);
            }
        }
        return expr;
    }

    protected final XQueryCompiler getXQueryCompiler() {
        if (xquery == null) {
            xquery = new Processor(false).newXQueryCompiler();
        //maybe set options
        }
        return xquery;
    }

    @Override
    public boolean isValid(String query) {
        try {
            cacheAwareCompile(query, true);
            return true;
        } catch (SaxonApiException e) {
        }
        return false;
    }
    private static final Object[] params = new Object[0];

    public Object[] getParams() {
        return params;
    }

    public List<?> resolve(String query, Object node, Object param) throws QueryResolverException {
        if (node == null) {
            throw new NullArgumentException("node");
        }

        if (node instanceof Node) {
            Node xmlNode = (Node) node;

            XQueryExecutable expr;
            XQueryEvaluator eval;
            try {
                expr = cacheAwareCompile(query, true);
                eval = expr.load();
                Source source = new DOMSource(xmlNode);
                eval.setSource(source);
            } catch (SaxonApiException e) {
                throw new InvalidQueryException(e);
            }
            Document root = XMLUtil.createDocument(null, null, null, null);
            Destination destination = new DOMDestination(root);
            eval.setDestination(destination);

            Iterator<XdmItem> iter = null;
            try {
                iter = eval.iterator();
            } catch (IllegalStateException e) {
                String errorMsg = NbBundle.getMessage(SaxonXQueryResolver.class, "SaxonXQueryResolver.updatingQueryNotAllowed");
                throw new InvalidQueryException(errorMsg);
            } catch (SaxonApiUncheckedException e) {
                throw new InvalidQueryException(e);
            }

            List<Object> ret = new ArrayList<Object>();
            while (iter.hasNext()) {
                XdmItem item = iter.next();
                if (item instanceof XdmNode) {
                    XdmNode xdmNode = (XdmNode) item;
                    Object obj = xdmNode.getExternalNode();
                    ret.add(obj);
                }
            }
            return ret;

        } else {
            throw new IllegalArgumentException("node is not an instance of org.w3c.dom.Node");
        }

    }

    public String supportedLanguage() {
        return LANG_XQUERY1;
    }

    public String mimeType() {
        return MIME_TYPE;
    }
}
